package com.clw.runtime.heap;

import com.clw.runtime.Class;
import com.sun.tools.classfile.ClassFile;
import com.sun.tools.classfile.ConstantPoolException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import sun.tools.java.ClassPath;

/**
 * Created by chengliwei on 2019/1/13.
 */
public class ClassLoader {

    private ClassPath classPath;
    private Map<String, Class> classMap;

    public ClassLoader(ClassPath classPath) {
        this.classPath = classPath;
        this.classMap = new HashMap<>();
    }

    public Class loadClass(String name) {
        if (classMap.get(name) != null) {
            return classMap.get(name);
        }
        return loadNonArrayClass(name);
    }

    private Class loadNonArrayClass(String name) {
        InputStream data = readClass(name);
        Class clazz = defineClass(data);
        return clazz;
    }

    private Class defineClass(InputStream data) {
        if (data == null) {
            return null;
        }
        try {
            ClassFile classFile = ClassFile.read(data);
            Class clazz = new Class(classFile);
            clazz.setClassLoader(this);
            resolveSuperClass(clazz);
            resolveInterfaces(clazz);
            classMap.put(clazz.getThisClassName(), clazz);
            return clazz;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ConstantPoolException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void resolveInterfaces(Class clazz) {
        String[] interfaceNames = clazz.getInterfaceNames();
        Class[] interfaceClasses = new Class[interfaceNames.length];
        for (int i = 0; i < interfaceNames.length; i++) {
            Class interfaceClazz = clazz.getClassLoader().loadClass(interfaceNames[i]);
            interfaceClasses[i] = interfaceClazz;
        }
        clazz.setInterfaceClasses(interfaceClasses);
    }

    private void resolveSuperClass(Class clazz) {
        if (clazz.getThisClassName() != "java/lang/Object") {
            clazz.setSuperClass(clazz.getClassLoader().loadClass(clazz.getSuperClassName()));
        }
    }

    private InputStream readClass(String name) {
        if (name == null) {
            return null;
        }
        InputStream inputStream = null;
        if (name.contains("/")) {
            name = name + ".class";
        }
        try {
            sun.tools.java.ClassFile file = classPath.getFile(name);
            inputStream = file.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return inputStream;
    }
}
